#include "stdafx.h"
#include "Terrain.h"

#include "Generators\DiamondSquare.h"
#include "Generators\Normalize.h"
#include "Generators\RandomNoise.h"
#include "Generators\Smooth.h"
#include "Generators\Voronoi.h"
#include "../../Framework/Toolkit.h"
#include "../../Framework/ContentManager.h"

namespace MyGame {

Terrain::Terrain(Game* game) : Mesh(game) {
    this->SetEffectFile("FractalTerrain.fx");
    this->drawPartialTerrain = false;
    this->drawUnderwater = true;
    this->heightMapScale = D3DXVECTOR3(1, 1, 1);
    this->heightMapLength = 100;
    this->heightMapWidth = 100;
    this->heightMap = NULL;
    this->terrainParts = NULL;
    this->visibilityRange = 100;
    this->visibleTerrainParts = NULL;

    this->heightWater = 0.1f;
    this->heightSand = 0.15f;
    this->heightGrass = 0.2f;
    this->heightRock = 0.7f;
    this->heightSnow = 1.0f;
}

Terrain::~Terrain(void) {
}

bool Terrain::GetDrawPartialTerrain() const {
    return this->drawPartialTerrain;
}

void Terrain::SetDrawPartialTerrain(bool val) {
    this->drawPartialTerrain = val;
}

FLOAT Terrain::GetHeightGrass() const {
    return this->heightGrass;
}

void Terrain::SetHeightGrass(FLOAT val) {
    this->heightGrass = val;
}

FLOAT Terrain::GetHeightRock() const {
    return this->heightRock;
}

void Terrain::SetHeightRock(FLOAT val) {
    this->heightRock = val;
}

FLOAT Terrain::GetHeightSand() const {
    return this->heightSand;
}

void Terrain::SetHeightSand(FLOAT val) {
    this->heightSand = val;
}

FLOAT Terrain::GetHeightSnow() const {
    return this->heightSnow;
}

void Terrain::SetHeightSnow(FLOAT val) {
    this->heightSnow = val;
}

FLOAT Terrain::GetHeightWater() const {
    return this->heightWater;
}

void Terrain::SetHeightWater(FLOAT val) {
    this->heightWater = val;
}

HeightMap* Terrain::GetHeightMap() const {
    return this->heightMap;
}

D3DXVECTOR3 Terrain::GetHeightMapScale() const {
    return this->heightMapScale;
}

void Terrain::SetHeightMapScale(D3DXVECTOR3 val) {
    this->heightMapScale = val;
}

FLOAT Terrain::GetVisibilityRange() const {
    return this->visibilityRange;
}

void Terrain::SetVisibilityRange(FLOAT val) {
    this->visibilityRange = val;
}

FLOAT Terrain::GetHeightFromSurface(D3DXVECTOR3 position) {
    const TreeBase<TerrainPart*>* terrainPart = this->terrainParts->FindChild(position);
    if (terrainPart == NULL) {
        return 0;
    }

    BOOL hit;
    FLOAT distenceToMesh;
    D3DXIntersect(terrainPart->GetItem()->GetMesh(), &D3DXVECTOR3(position), &D3DXVECTOR3(0, 1, 0), &hit, NULL, NULL, NULL, &distenceToMesh, NULL, NULL);
    if (hit == TRUE) {
        return distenceToMesh;
    }

    return 0;
}

Quadtree<TerrainPart*>* Terrain::GetTerrainParts() const {
    return this->terrainParts;
}

void Terrain::SetDrawUnderwater(bool val) {
    this->drawUnderwater = val;
}

void Terrain::SetHeightMapDimensions(int width, int length) {
    this->heightMapLength = length;
    this->heightMapWidth = width;
}

void Terrain::Draw(FLOAT timeDelta) {
    if (this->effectFile == NULL) {
        __super::Draw(timeDelta);
        return;
    }

    D3DXVECTOR3 vLightDir = D3DXVECTOR3(1, 1, 1);
    D3DXVec3Normalize(&vLightDir, &vLightDir);
    D3DXCOLOR vLightDiffuse = D3DXCOLOR(1, 1, 1, 1);
    this->effect->SetValue("g_LightDir", &vLightDir, sizeof(D3DXVECTOR3));
    this->effect->SetValue("g_LightDiffuse", &vLightDiffuse, sizeof(D3DXCOLOR)) ;

    D3DXMATRIX mWorld = this->GetWorldMatrix();
    D3DXMATRIX mWorldViewProjection = mWorld * this->GetGame()->GetCamera()->GetViewMatrix() * this->GetGame()->GetCamera()->GetProjectionMatrix();
    this->effect->SetMatrix("g_mWorldViewProjection", &mWorldViewProjection) ;
    this->effect->SetMatrix("g_mWorld", &mWorld) ;
    this->effect->SetFloat("g_fTime", timeDelta) ;
    this->effect->SetFloat("g_fMaxElev", this->heightMapScale.y) ;
    this->effect->SetTechnique("RenderSceneWithTexture1Light");

    if (this->drawPartialTerrain) {
        if (this->cullMode != D3DCULL_NONE && this->GetGame()->GetCamera()->GetMirror()) {
            if (this->cullMode == D3DCULL_CCW) {
                this->GetGame()->GetGraphicsDevice()->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);
            } else if (this->cullMode == D3DCULL_CW) {
                this->GetGame()->GetGraphicsDevice()->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
            }
        } else {
            this->GetGame()->GetGraphicsDevice()->SetRenderState(D3DRS_CULLMODE, this->cullMode);
        }

        UINT iPass, cPasses;
        this->effect->Begin(&cPasses, 0);
        for (iPass = 0; iPass < cPasses; iPass++) {
            this->effect->BeginPass(iPass);
            this->GetGame()->GetGraphicsDevice()->SetMaterial(&d3d::WHITE_MTRL);
            this->GetGame()->GetGraphicsDevice()->SetTexture(0, this->textureWater);
            for (vector<TreeBase<TerrainPart*>*>::iterator it = this->visibleTerrainParts->begin();
                    it != this->visibleTerrainParts->end();
                    ++it) {
                this->DrawTerrainParts((Quadtree<TerrainPart*>*)*it);
            }

            this->effect->EndPass() ;
        }

        this->effect->End() ;
    } else {
        __super::Draw(timeDelta);
    }
}

void Terrain::DrawSubset(DWORD attribId) {
    this->mesh->GetMesh()->DrawSubset(0);
    if (this->drawUnderwater) {
        this->mesh->GetMesh()->DrawSubset(1);
    }
}

void Terrain::DrawTerrainParts(Quadtree<TerrainPart*>* quadtree) {
    if (quadtree->GetChildren()->size() > 0) {
        for (vector<TreeBase<TerrainPart*>*>::iterator it = quadtree->GetChildren()->begin(); it != quadtree->GetChildren()->end(); ++it) {
            this->DrawTerrainParts((Quadtree<TerrainPart*>*)*it);
        }
    } else {
        quadtree->GetItem()->GetMesh()->DrawSubset(0);
        if (this->drawUnderwater) {
            quadtree->GetItem()->GetMesh()->DrawSubset(1);
        }
    }
}

void Terrain::GenerateFaceNormals() {
    vector<D3DXVECTOR3> normals;
    for ( int i = 0; i < (this->heightMapWidth - 1) * (this->heightMapLength - 1) * 6; i += 3) {
        D3DXVECTOR3 v2minusv0 = D3DXVECTOR3(
                                    (*this->vertices)[(*this->indices)[i + 1]].x - (*this->vertices)[(*this->indices)[i]].x,
                                    (*this->vertices)[(*this->indices)[i + 1]].y - (*this->vertices)[(*this->indices)[i]].y,
                                    (*this->vertices)[(*this->indices)[i + 1]].z - (*this->vertices)[(*this->indices)[i]].z);
        D3DXVECTOR3 v1minusv0 = D3DXVECTOR3(
                                    (*this->vertices)[(*this->indices)[i + 2]].x - (*this->vertices)[(*this->indices)[i]].x,
                                    (*this->vertices)[(*this->indices)[i + 2]].y - (*this->vertices)[(*this->indices)[i]].y,
                                    (*this->vertices)[(*this->indices)[i + 2]].z - (*this->vertices)[(*this->indices)[i]].z);

        D3DXVECTOR3 normal;
        D3DXVec3Cross(&normal, &v2minusv0, &v1minusv0);
        D3DXVec3Normalize(&normal, &normal);
        normals.push_back(normal);
    }

    // For each vertex, sum the normals indexed by the normal lookup table.
    int j = 0;
    for (int i = 0; i < (this->heightMapWidth * this->heightMapLength); i++) {
        D3DXVECTOR3 avgNormal = D3DXVECTOR3(0, 0, 0);

        // Find all the triangles that this vertex is a part of.
        for (j = 0; j < 6; j++) {
            int triIndex;
            triIndex = this->normalLookupTable[i * 6 + j];

            // If the triangle index is valid, get the normal and average it in.
            if ( triIndex != -1 ) {
                avgNormal += normals[triIndex];
            } else {
                break;
            }
        }

        // Complete the averaging step by dividing & normalizing.
        avgNormal /= (FLOAT)j;
        D3DXVec3Normalize(&avgNormal, &avgNormal);

        // Set the vertex normal to this new normal.
        (*this->vertices)[i].nx = avgNormal.x;
        (*this->vertices)[i].ny = avgNormal.y;
        (*this->vertices)[i].nz = avgNormal.z;
    }  // For each vertex
}
void Terrain::GenerateIndices() {
    int indiceCount = (this->heightMapLength - 1) * (this->heightMapWidth - 1) * 6;
    this->indices = new vector<DWORD>(indiceCount);
    int i = 0;
    for (int z = 0; z < this->heightMapLength - 1; z++) {
        for (int x = 0; x < this->heightMapWidth - 1; x++) {
            DWORD vertexIndex = x + z * this->heightMapWidth;
            (*this->indices)[i++] = vertexIndex;
            (*this->indices)[i++] = vertexIndex + this->heightMapWidth;
            (*this->indices)[i++] = vertexIndex + this->heightMapWidth + 1;
            (*this->indices)[i++] = vertexIndex;
            (*this->indices)[i++] = vertexIndex + this->heightMapWidth + 1;
            (*this->indices)[i++] = vertexIndex + 1;
        }
    }
}

void Terrain::GenerateNormalLookupTable() {
    // Each vertex may be a part of up to 6 triangles in the grid, so
    // create a buffer to hold a pointer to the normal of each neighbor.
    int buffersize = this->heightMapWidth * this->heightMapLength * 6;

    this->normalLookupTable.resize(buffersize);
    for (int i = 0; i < buffersize; i++)
        this->normalLookupTable[i] = -1;

    // Now that the table is initialized, populate it with the triangle data.

    // For each triangle
    //   For each vertex in that triangle
    //     Append the triangle number to lookuptable[vertex]
    for (int i = 0; i < 2*(this->heightMapWidth - 1) * (this->heightMapLength - 1); i++) {
        for (int j = 0; j < 3; j++) {
            // Find the next empty slot in the vertex's lookup table "slot"
            for (int k = 0; k < 6; k++) {
                int vertex = (*this->indices)[i * 3 + j];
                if (this->normalLookupTable[vertex * 6 + k] == -1) {
                    this->normalLookupTable[vertex * 6 + k] = i;
                    break;
                }
            }
        }  // For each vertex that is part of the current triangle
    } // For each triangle
}

void Terrain::GenerateTerrain() {
    srand(0);
    this->heightMap = new HeightMap(this->heightMapWidth, this->heightMapLength);
    RandomNoise* randomNoise = new RandomNoise();
    randomNoise->SetBlend(0.5f);
    randomNoise->SetMaxHeight(0.1f);
    randomNoise->SetMinHeight(0.0f);
    randomNoise->GenerateNormalizeBlend(this->heightMap);
    Smooth* smooth = new Smooth();
    smooth->SetBlend(0.5f);
    smooth->SetIterations(3);
    smooth->Generate(this->heightMap);
    // Diamond square is not used anymore in combination with Voronoi
    //DiamondSquare* diamondSquare = new DiamondSquare();
    //diamondSquare->SetBlend(0.2f);
    //diamondSquare->GenerateNormalizeBlend(this->heightMap);
    Voronoi* voronoi = new Voronoi();
    voronoi->SetBlend(0.8f);
    voronoi->GenerateNormalizeBlend(this->heightMap);
    Normalize* normalize = new Normalize();
    normalize->SetScale(this->heightMapScale.y);
    normalize->Generate(this->heightMap);

    // FactoryGenerator is not used but this piece of code is kept 
    // kept for future reference
    //srand(0);
    //FactoryGenerator* factoryGenerator = new FactoryGenerator();
    //std::vector<Factory*> factories = factoryGenerator->Generate(0);
    //this->heightMap = new HeightMap(width, length);
    //std::vector<Factory*>::iterator i;
    //for (i = factories.begin(); i != factories.end(); i++)
    //{
    //	Generator* generator = (*i)->Generate();
    //	generator->GenerateNormalizeBlend(this->heightMap);
    //}
    //smooth->GenerateNormalizeBlend(this->heightMap);
    //normalize->Generate(this->heightMap);
}

void Terrain::GenerateVertices() {
    int alongLength = heightMap->GetLength();
    int alongWidth = heightMap->GetWidth();
    this->vertices = new vector<Vertex>(alongLength * alongWidth);
    float halfWidth = ((float)alongWidth - 1.0f) / 2.0f;
    float halfLength = ((float)alongLength - 1.0f) / 2.0f;
    FLOAT** map = heightMap->GetMap();
    for (int x = 0; x < alongWidth; x++) {
        for (int z = 0; z < alongLength; z++) {
            (*this->vertices)[z * alongWidth + x] = Vertex(
                                                        (float)x - halfWidth,
                                                        (float)map[x][z],
                                                        (float)z - halfLength,
                                                        0, 1, 0,
                                                        (float)x / (alongWidth - 1),
                                                        (float)z / (alongLength - 1));
        }
    }
}

void Terrain::Initialize() {
    this->GenerateTerrain();
    this->GenerateVertices();
    this->GenerateIndices();
    this->GenerateNormalLookupTable();
    this->GenerateFaceNormals();
    this->InitializeMesh();

    float halfWidth = ((float)this->heightMapWidth - 1.0f) / 2.0f;
    float halfLength = ((float)this->heightMapLength - 1.0f) / 2.0f;
    d3d::BoundingBox* boundingBox = new d3d::BoundingBox();
    boundingBox->_min = D3DXVECTOR3(-halfWidth, 0, -halfLength);
    boundingBox->_max = D3DXVECTOR3(halfWidth, this->heightMapScale.y, halfLength);
    this->terrainParts = new Quadtree<TerrainPart*>(boundingBox, 20, NULL);

    this->InitializeQuadtree(this->terrainParts);

    d3d::Delete(this->indices);
    d3d::Delete(this->vertices);
}

void Terrain::InitializeMesh() {
    ID3DXMesh* mesh;
    int numFaces = (this->heightMap->GetLength() - 1) * (this->heightMap->GetWidth() - 1) * 2;
    int numVertices = this->heightMap->GetLength() * this->heightMap->GetWidth();
    D3DXCreateMeshFVF(
        numFaces,
        numVertices,
        D3DXMESH_MANAGED | D3DXMESH_32BIT,
        Vertex::FVF,
        this->GetGame()->GetGraphicsDevice(),
        &mesh);

    Vertex* vertices = NULL;
    mesh->LockVertexBuffer(0, (void**)&vertices);
    memcpy(vertices, &(*this->vertices)[0], sizeof(Vertex) * this->vertices->size());
    mesh->UnlockVertexBuffer();

    DWORD* indices = 0;
    mesh->LockIndexBuffer(0, (void**)&indices);
    memcpy(indices, &(*this->indices)[0], sizeof(DWORD) * this->indices->size());
    mesh->UnlockVertexBuffer();

    DWORD* attributeBuffer = 0;
    mesh->LockAttributeBuffer(0, &attributeBuffer);

    FLOAT underWater = this->heightWater * this->heightMapScale.y;
    int i = 0;
    for (vector<DWORD>::iterator it = this->indices->begin(); it != this->indices->end(); ++it) {
        Vertex v1 = (*this->vertices)[*it++];
        Vertex v2 = (*this->vertices)[*it++];
        Vertex v3 = (*this->vertices)[*it];
        if  (v1.y < underWater && v2.y < underWater && v3.y < underWater) {
            attributeBuffer[i] = 1;
        } else {
            attributeBuffer[i] = 0;
        }

        i++;
    }

    mesh->UnlockAttributeBuffer();

    DWORD* adjacencyBuffer = new DWORD[mesh->GetNumFaces() * 3];
    HRESULT hr = mesh->GenerateAdjacency(0.0f, adjacencyBuffer);
    mesh->OptimizeInplace(
        D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_COMPACT | D3DXMESHOPT_VERTEXCACHE,
        adjacencyBuffer,
        NULL, NULL, NULL);

    this->mesh = new D3DXMesh(mesh);
}

void Terrain::InitializeQuadtree(Quadtree<TerrainPart*>* quadtree) {
    quadtree->SetItem(new TerrainPart(this->GetGame()));
    if (quadtree->GetChildren()->size() == 0) {
        float halfWidth = ((float)this->heightMapWidth - 1.0f) / 2.0f;
        float halfLength = ((float)this->heightMapLength - 1.0f) / 2.0f;
        d3d::BoundingBox* boundingBox = quadtree->GetBoundingBox();
        int fromX = (int)(boundingBox->_min.x + halfWidth);
        int fromZ = (int)(boundingBox->_min.z + halfLength);
        int toX = (int)(boundingBox->_max.x + halfWidth);
        int toZ = (int)(boundingBox->_max.z + halfLength);
        HeightMap* part = HeightMap::Crop(this->heightMap, fromX, fromZ, toX, toZ);

        int partWidth = toX - fromX + 1;
        int partLength = toZ - fromZ + 1;
        vector<Vertex>* verticesPart = new vector<Vertex>(partWidth * partLength);
        d3d::BoundingBox bb;
        bb._min.y = boundingBox->_min.y;
        bb._max.y = boundingBox->_max.y;
        for (int x = 0; x < partWidth; ++x) {
            for (int z = 0; z < partLength; ++z) {
                Vertex v = (*this->vertices)[(z + fromZ) * this->heightMapWidth + (x + fromX)];
                (*verticesPart)[z * partWidth + x] = v;

                if (v.x < bb._min.x) {
                    bb._min.x = v.x;
                } else if (v.x > bb._max.x) {
                    bb._max.x = v.x;
                }

                if (v.z < bb._min.z) {
                    bb._min.z = v.z;
                } else if (v.z > bb._max.z) {
                    bb._max.z = v.z;
                }
            }
        }

        boundingBox->_max = bb._max;
        boundingBox->_min = bb._min;

        quadtree->GetItem()->SetHeightMap(part);
        quadtree->GetItem()->SetVertices(verticesPart);
        quadtree->GetItem()->SetWaterHeight(this->heightWater * this->heightMapScale.y);
        quadtree->GetItem()->InitializeMesh(fromX - halfWidth, fromZ - halfWidth);
    }

    if (quadtree->GetChildren()->size() > 0) {
        for (vector<TreeBase<TerrainPart*>*>::iterator it = quadtree->GetChildren()->begin(); it != quadtree->GetChildren()->end(); ++it) {
            this->InitializeQuadtree((Quadtree<TerrainPart*>*)*it);
        }

        d3d::BoundingBox* boundingBox = quadtree->GetBoundingBox();
        d3d::BoundingBox bb;
        bb._min.y = boundingBox->_min.y;
        bb._max.y = boundingBox->_max.y;

        for (vector<TreeBase<TerrainPart*>*>::iterator it = quadtree->GetChildren()->begin(); it != quadtree->GetChildren()->end(); ++it) {
            d3d::BoundingBox* childBox = (*it)->GetBoundingBox();
            if (childBox->_min.x < bb._min.x) {
                bb._min.x = childBox->_min.x;
            }

            if (childBox->_max.x > bb._max.x) {
                bb._max.x = childBox->_max.x;
            }

            if (childBox->_min.z < bb._min.z) {
                bb._min.z = childBox->_min.z;
            }

            if (childBox->_max.z > bb._max.z) {
                bb._max.z = childBox->_max.z;
            }
        }

        boundingBox->_max = bb._max;
        boundingBox->_min = bb._min;
    }
};

void Terrain::LoadContent() {
    __super::LoadContent();

    if (this->effectFile == NULL) {
        return;
    }

    D3DXCOLOR colorMtrlDiffuse(1.0f, 1.0f, 1.0f, 1.0f);
    D3DXCOLOR colorMtrlAmbient(0.35f, 0.35f, 0.35f, 0);

    this->effect->SetValue("g_MaterialAmbientColor", &colorMtrlAmbient, sizeof(D3DXCOLOR));
    this->effect->SetValue("g_MaterialDiffuseColor", &colorMtrlDiffuse, sizeof(D3DXCOLOR));

    this->textureGrass = this->GetGame()->GetContentManager()->LoadTexture("Grass.jpg");
    this->textureRock = this->GetGame()->GetContentManager()->LoadTexture("Rock.jpg");
    this->textureSand = this->GetGame()->GetContentManager()->LoadTexture("Sand.jpg");
    this->textureSnow = this->GetGame()->GetContentManager()->LoadTexture("Snow.jpg");
    this->textureWater = this->GetGame()->GetContentManager()->LoadTexture("Water.jpg");

    this->effect->SetTexture("g_WaterTexture", this->textureWater);
    this->effect->SetTexture("g_SandTexture", this->textureSand);
    this->effect->SetTexture("g_GrassTexture", this->textureGrass);
    this->effect->SetTexture("g_RockTexture", this->textureRock);
    this->effect->SetTexture("g_SnowTexture", this->textureSnow);

    this->effect->SetFloat("g_fZoneWater", 0);
    this->effect->SetFloat("g_fZonePlains", this->heightSand);
    this->effect->SetFloat("g_fZoneHills", this->heightGrass);
    this->effect->SetFloat("g_fZoneAlpine", this->heightRock);
    this->effect->SetFloat("g_fZoneMountain", this->heightSnow);

    this->mesh->GetMaterials()->push_back(d3d::WHITE_MTRL);
    this->mesh->GetTextures()->push_back(this->textureWater);
}

void Terrain::UpdateVisibleParts() {
    if (this->visibleTerrainParts != NULL) {
        d3d::Delete(this->visibleTerrainParts);
    }

    if (this->drawPartialTerrain) {
        this->GetGame()->GetCamera()->SetVisibilityRange(this->visibilityRange);
        this->visibleTerrainParts = this->terrainParts->FindVisible(this->GetGame()->GetCamera());
    }
}

}
